package pacman.ml.features;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;

import pacman.game.Game;

// singleton class
public abstract class FeatureExtractor {
	
	
	private static volatile HashMap<Class<?>, HashMap<Float, FeatureExtractor>> singletons = new HashMap<Class<?>, HashMap<Float, FeatureExtractor>>();
	// private static volatile FeatureExtractor instance = null;
    // singleton code is experimental and not used yet
	public static FeatureExtractor getInstance(Class<?> type, float arg) {
		HashMap<Float, FeatureExtractor> argmap = null;
		if(singletons.containsKey(type)) {
			argmap = singletons.get(type);
			if(argmap.containsKey(arg)) {
				return argmap.get(arg);
			}
		}
		FeatureExtractor instance = null;
		// suppress warnings about using Class without giving a type
        @SuppressWarnings("rawtypes")
		Class[] argTypes = {float.class};
        Object[] args = {arg};
        try {
        	instance = (FeatureExtractor)type.getConstructor(argTypes).newInstance(args);
        } catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        if(instance != null) {
        	if(argmap != null) {
        		argmap.put(arg, instance);
        	}
        	else {
        		argmap = new HashMap<Float, FeatureExtractor>();
        		argmap.put(arg, instance);
        		singletons.put(type, argmap);
        	}
        }
        return instance;
    }
	
	private float cache;   // stores computed feature value so that if extract is called multiple times, it does not need to be recomputed
	private int gameTotalTime = -1;
	public float GetValue(Game game) {
		int updatedTotalTime = game.getTotalTime();
		if(updatedTotalTime == gameTotalTime) {
			return cache;
		}
		gameTotalTime = updatedTotalTime;
		cache = Extract(game);
		return cache;
	}
	protected abstract float Extract(Game game);
	public float GetLastValue() {
		if(gameTotalTime < 0) {   // special case:  return a null value
			return 0;
		}
		return cache;
	}
}